permits <- get_egis_table(db = "housing", table = "tbl_Permit")
2019-03-14 19:56:39: Retrieving table housing.tbl_Permit
Error in ogrInfo(dsn = dsn, layer = layer, encoding = encoding, use_iconv = use_iconv,  : 
  Cannot open layer

First need to join up the real property data (Open Baltimore) the sales data (provided by Steve, and with deed dates from January 1, 2010 through October 2018) so we have a neighborhood for as many sales as we can.

sales <- sales %>% rename(sales.block = Block, sales.lot = Lot)
Error in .f(.x[[i]], ...) : object 'Block' not found
real.prop <- real.prop %>%
  mutate(real.block.clean = gsub("^0+", "", real.block),
         real.lot.clean = gsub("^0+", "", real.lot))
sales <- sales %>%
  mutate(sales.block.clean = gsub("^0+", "", sales.block),
         sales.lot.clean = gsub("^0+", "", sales.lot))
sales <- sales %>%
  left_join(real.prop, 
            by = c("sales.block.clean" = "real.block.clean",
                   "sales.lot.clean" = "real.lot.clean")
            )
sales %>% count(is.na(real.block), is.na(real.lot))

1,153 sales didn’t match to a block-lot in the real property table, which means that the block-lot jointly was not in the real prop table.

Also, there are about 16,000 properties in the real prop table that don’t have a neighborhood.

real.prop %>% count(is.na(neighborhood))

So after joining we end up with 9,428 sales that don’t have a neighborhood.

sales %>% count(!is.na(neighborhood))

The real property table also gives if it is principal residence or not, so we’ll also filter for the sales that are for principal residences.

sales %>% count(rescode)

Distribution of city-wide 2018 sales prices:

sales %>%
  filter(year(deed.date) == 2018) %>%
  ggplot(aes(`Sales Price`)) +
  geom_histogram() +
  theme_iteam_google_docs() +
  xlim(c(0, 500000))

quantile(sales$`Sales Price`, 0.85)
   85% 
275000 

Bring in permit data

permits %>% glimpse()
Observations: 836,816
Variables: 45
$ ID_Permit        <int> 375642224, 375642225, 375642226, 375642227, 37...
$ csm_caseno       <fct> 000000001, 000000002, 000000003, 000000004, 00...
$ csm_plan_year    <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ csm_plans_number <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ csm_description  <fct> issued through building inspection, install ga...
$ csm_expr_date    <dttm> 1994-06-06, 1994-06-04, 1993-06-06, 1994-06-0...
$ csm_finaled_date <dttm> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N...
$ csm_issued_date  <dttm> 1993-09-30, 1993-06-14, 1993-06-14, 1993-06-1...
$ csm_name_first   <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ csm_name_last    <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ csm_name_mi      <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ csm_projname     <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ csm_recd_by      <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ csm_recd_date    <dttm> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N...
$ csm_status       <fct> EXP, EXP, EXP, EXP, EXP, EXP, EXP, EXP, EXP, E...
$ csm_frozen       <fct> N, N, N, N, N, N, N, N, N, N, F, N, N, N, N, N...
$ csm_auto_cond    <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ csm_updateby     <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ csm_updated      <dttm> 2005-08-07, 2005-08-07, 2005-08-07, 2005-08-0...
$ csm_projno       <fct> 000000001, 000000002, 000000003, 000000004, 00...
$ prc_avp_no       <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
$ csm_target_date  <dttm> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N...
$ case_type        <fct> COM, COM, COM, COM, COM, COM, COM, COM, COM, C...
$ PLANADDRESS      <fct> 0000 COUNTER, 1067 CAMERON ROAD, 4266 CLYDESDA...
$ prc_parcel_no    <fct> 9948 948, 5142 034, 3575C010, 5164 022, 3355 0...
$ com_type_work    <fct> OTH, OTH, AA, OTH, OTH, OTH, OTH, OTH, OTH, AA...
$ com_sprinklers   <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ com_existing_use <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ csm_type_work    <fct> OTH, OTH, AA, OTH, OTH, OTH, OTH, OTH, OTH, AA...
$ csm_use          <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ PlansNum         <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ csm_st_name      <fct> COUNTER, CAMERON ROAD, CLYDESDALE AVE, KENILWO...
$ csm_st_number    <fct> 0000, 1067, 4266, 5313, 2518, 4217, 3527, 0720...
$ csm_st_pfx       <fct> NA, NA, NA, NA, NA, NA, NA, NA, E, NA, NA, NA,...
$ prc_block_no     <fct> 9948 , 5142, 3575C, 5164, 3355, 5749, 5555, 76...
$ prc_lot          <fct> 948, 034, 010, 022, 010, 017, 194, 036, 048, 0...
$ prc_neighborhood <fct> NA, CAMERON VILLAGE, MEDFIELD, KENILWORTH PARK...
$ PlanURL          <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ prc_hi_dist      <int> NA, 570, 540, 570, 672, 480, 470, 980, 551, 62...
$ csm_cost         <dbl> 0, 2000, 2290, 1400, 6600, 5800, 4500, 5350, 7...
$ csm_mastno       <fct> 000000001, 000000002, 000000003, 000000004, 00...
$ BlockLot         <fct> 9948 948, 5142034, 3575C010, 5164022, 3355010,...
$ csm_id           <fct> 200243175721, 200243175722, 200243175723, 2002...
$ Applicant        <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ Lessee           <fct> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
permits %>% count(csm_status)
permits %>% count(csm_type_work)

Neighborhood Summary Table, 2015-2017

meet.criteria <- sales %>%
  filter(year(deed.date) %in% c(2015, 2016, 2017),
         !is.na(neighborhood),
         `How Conveyed` == 1,
         !grepl("NOT", rescode)) %>%
  nrow

We have 16112 samples to work with that are in 2015-2017, have a neighborhood, were an arms-length sale, and are the principal residence.

sales.summary.15_17.by.hood <- sales %>%
  filter(year(deed.date) %in% c(2015, 2016, 2017),
         !is.na(neighborhood),
         `How Conveyed` == 1,
         !grepl("NOT", rescode)) %>%
  group_by(neighborhood) %>%
  summarise(hood.n = n(),
            hood.mean = mean(`Sales Price`),
            hood.median = median(`Sales Price`),
            hood.std = sqrt(sum((`Sales Price`-hood.mean)^2/(hood.n-1))),
            hood.95th = quantile(`Sales Price`, probs = .95),
            hood.98th = quantile(`Sales Price`, probs = .98),
            hood.99th = quantile(`Sales Price`, probs = .99))
sales.summary.15_17.by.hood  

Which neighborhoods have less than 20 sales meeting the criteria?

sales.summary.15_17.by.hood %>%
  filter(hood.n < 20)

84 neighborhoods have less than 20 sales meeting the criteria. We’ll exclude them going forward so we have a reasonable sample size.

# Join the summaries to the neighborhood boundaries
hoods@data <- hoods@data %>% 
  left_join(sales.summary.15_17.by.hood,
            by = c("label" = "neighborhood"))

98th Percentile

Criteria & Results

sales.hood.98th <- sales %>%
  left_join(sales.summary.15_17.by.hood,
            by = c("neighborhood" = "neighborhood")) %>%
  filter(year(deed.date) == 2018,
         hood.n >= 20,
         `Sales Price` >= hood.98th,
         `How Conveyed` == 1,
         !grepl("NOT", rescode)) %>%
  arrange(neighborhood)
result.sales <- nrow(sales.hood.98th)

There are 167 sales that meet the following criteria:

  • Deed date was between January 1, 2018 and October 5, 2018
  • Arms-length sale
  • Principal residence
  • Neighborhood had at least 20 sales
  • 98th percentile for sales prices for their neighborhood.

(If this yield isn’t high enough we can bump it down to the 95th percentile.)

sales.hood.98th$long <- lapply(sales.hood.98th$location.coordinates, function(x) x[1]) %>% unlist()
sales.hood.98th$lat <- lapply(sales.hood.98th$location.coordinates, function(x) x[2]) %>% unlist()
sales.hood.98th.geo <- sales.hood.98th %>% filter(!is.na(long))
  
sales.hood.98th.geo <- SpatialPointsDataFrame(
  sales.hood.98th.geo %>% select(long, lat), 
  sales.hood.98th.geo,
  proj4string = CRS("+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0"))
sales.hood.98th.geo <- 
  spTransform(
    sales.hood.98th.geo, 
    CRSobj = CRS("+init=epsg:4326 +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0")
    )

Map

library(htmltools)
hoods.labels <- paste0(
  hoods$label,
  "<br>Median Sales, 2015-2017: ", as.character(hoods$hood.median)
  
)
sale.labels <- paste0(
  sales.hood.98th.geo$`House #`, " ",
  sales.hood.98th.geo$`Street Name`, " ",
  sales.hood.98th.geo$Suffix, 
  "<br>Sale Price in 2018: ", 
  as.character(sales.hood.98th.geo$`Sales Price`),
  "<br>New Owner: ", sales.hood.98th.geo$new.owner
)
leaflet() %>%
  setView(lng = -76.6, lat = 39.3, zoom = 11) %>%
  addProviderTiles(providers$Stamen.TonerLite) %>% 
  addPolygons(data = hoods, 
              weight = 2, 
              color = "black",
              opacity = 0.5,
              fillOpacity = 0, 
              label = ~lapply(hoods.labels, HTML)) %>%
  addCircleMarkers(data = sales.hood.98th.geo, 
                   radius = 2,
                   label = ~lapply(sale.labels, HTML))

Full list

sales.hood.98th 

Detect jumps

#look for temporal jumps in prices

99th Percentile

Criteria & Results

sales.hood.99th <- sales %>%
  left_join(sales.summary.15_17.by.hood,
            by = c("neighborhood" = "neighborhood")) %>%
  filter(year(deed.date) == 2018,
         hood.n >= 20,
         `Sales Price` >= hood.99th,
         `How Conveyed` == 1,
         !grepl("NOT", rescode)) %>%
  arrange(neighborhood)
result.sales <- nrow(sales.hood.99th)

There are 167 sales that meet the following criteria:

  • Deed date was between January 1, 2018 and October 5, 2018
  • Arms-length sale
  • Principal residence
  • Neighborhood had at least 20 sales
  • 99th percentile for sales prices for their neighborhood.

(If this yield isn’t high enough we can bump it down to the 95th percentile.)

sales.hood.99th$long <- lapply(sales.hood.99th$location.coordinates, function(x) x[1]) %>% unlist()
sales.hood.99th$lat <- lapply(sales.hood.99th$location.coordinates, function(x) x[2]) %>% unlist()
sales.hood.99th.geo <- sales.hood.99th %>% filter(!is.na(long))
  
sales.hood.99th.geo <- SpatialPointsDataFrame(
  sales.hood.99th.geo %>% select(long, lat), 
  sales.hood.99th.geo,
  proj4string = CRS("+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0"))
sales.hood.99th.geo <- 
  spTransform(
    sales.hood.99th.geo, 
    CRSobj = CRS("+init=epsg:4326 +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0")
    )

Map

library(htmltools)
hoods.labels <- paste0(
  hoods$label,
  "<br>Median Sales, 2015-2017: ", as.character(hoods$hood.median)
  
)
sale.labels <- paste0(
  sales.hood.99th.geo$`House #`, " ",
  sales.hood.99th.geo$`Street Name`, " ",
  sales.hood.99th.geo$Suffix, 
  "<br>Sale Price in 2018: ", 
  as.character(sales.hood.99th.geo$`Sales Price`),
  "<br>New Owner: ", sales.hood.99th.geo$new.owner
)
leaflet() %>%
  setView(lng = -76.6, lat = 39.3, zoom = 11) %>%
  addProviderTiles(providers$Stamen.TonerLite) %>% 
  addPolygons(data = hoods, 
              weight = 2, 
              color = "black",
              opacity = 0.5,
              fillOpacity = 0, 
              label = ~lapply(hoods.labels, HTML)) %>%
  addCircleMarkers(data = sales.hood.99th.geo, 
                   radius = 2,
                   label = ~lapply(sale.labels, HTML))
emily.list <- c("4011 BARRINGTON",
                "DORCHESTER",
                "2319 MONTICELLO")
sales.hood.98th.geo@data %>% 
  filter(grepl(paste(emily.list, collapse="|"), propertyaddress))

In Middle Neighborhood, 99th Percentile for Neighborhood, Over $250k, Over $10k Permit Activity

The following criteria are used below:

permits.recent <- permits %>% 
  filter(csm_issued_date >= "2017-01-01") %>%
  mutate(permit.block.clean = gsub("^0+", "", prc_block_no),
         permit.lot.clean = gsub("^0+", "", prc_lot))
permits.recent.summary <- permits.recent %>%
  group_by(permit.block.clean, permit.lot.clean) %>%
  summarise(permit.count = n(),
            permit.total.value = sum(csm_cost, na.rm = T))
mid.hoods <- hmt.hood %>% filter(`Predominant Code Ignoring Non-Residential` %in% c("D", "E", "F", "G", "H"))
sales.99th.mid.hood <- subset(sales.hood.99th.geo, tolower(neighborhood) %in% tolower(mid.hoods$Neighborhood))
sales.99th.mid.hood.over.250k <- subset(sales.99th.mid.hood,
                                        `Sales Price` > 250000)
sales.99th.mid.hood.over.250k@data %>% nrow
[1] 45
sales.99th.mid.hood.over.250k@data <- sales.99th.mid.hood.over.250k@data %>%
  left_join(permits.recent.summary, 
            by = c("sales.block.clean" = "permit.block.clean",
                   "sales.lot.clean" = "permit.lot.clean"))

Further filter for permit value totals over $10,000.

Results in 27 properties.

sales.99th.mid.hood.over.250k.10k.permit@data %>% nrow
[1] 27
mid.hoods.geo <- subset(hoods, 
                        tolower(label) %in% tolower(mid.hoods$Neighborhood))
mid.hoods.labels <- paste0(
  mid.hoods.geo$label,
  "<br>Median Sales, 2015-2017: ", as.character(mid.hoods.geo$hood.median)
  
)
sale.labels <- paste0(
  sales.99th.mid.hood.over.250k.10k.permit$`House #`, " ",
  sales.99th.mid.hood.over.250k.10k.permit$`Street Name`, " ",
  sales.99th.mid.hood.over.250k.10k.permit$Suffix, 
  "<br>Sale Price in 2018: ", 
  as.character(sales.99th.mid.hood.over.250k.10k.permit$`Sales Price`),
  "<br>New Owner: ", sales.99th.mid.hood.over.250k.10k.permit$new.owner,
  "<br>Permits Issued from 2017-2018: ", sales.99th.mid.hood.over.250k.10k.permit$permit.count,
  "<br>Total Permit Value from 2017-2018: ", sales.99th.mid.hood.over.250k.10k.permit$permit.total.value
)
leaflet() %>%
  setView(lng = -76.6, lat = 39.3, zoom = 11) %>%
  addProviderTiles(providers$Stamen.TonerLite) %>% 
  addPolygons(data = hoods, 
              weight = 2, 
              
              color = "black",
              opacity = 0.5,
              fillOpacity = 0, 
              label = ~lapply(hoods.labels, HTML)) %>%
  addPolygons(data = mid.hoods.geo, 
              weight = 2, 
              #color = "black",
              opacity = 0.0,
              fillOpacity = .2,
              fillColor = iteam.colors[3],
              label = ~lapply(mid.hoods.labels, HTML)) %>%
  addCircleMarkers(data = sales.99th.mid.hood.over.250k.10k.permit, 
                   color = iteam.colors[1],
                   opacity = 1,
                   radius = 2,
                   label = ~lapply(sale.labels, HTML))

Full List

LS0tDQp0aXRsZTogIlJlY2VudCBTYWxlcyBPdXRsaWVycyINCmF1dGhvcjogIkp1c3RpbiBFbHN6YXN6LCBNYXlvcidzIE9mZmljZSBvZiBJbm5vdmF0aW9uIg0KZW1haWw6ICJqdXN0aW4uZWxzemFzekBiYWx0aW1vcmVjaXR5LmdvdiINCmRhdGU6ICJUaHVyc2RheSwgRmVicnVhcnkgMjgsIDIwMTkiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgZmlnX2hlaWdodDogNQ0KICAgIGZpZ193aWR0aDogMTANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMg0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0UsIGVjaG8gPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBjYWNoZSA9IFRSVUV9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IEZBTFNFLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEYsIGluY2x1ZGUgPSBULA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA1KQ0KYGBgDQoNCg0KYGBge3J9DQpzb3VyY2UoIi4uL3NyYy8wMF9pbml0aWFsaXplLlIiKQ0Kc2FsZXMgPC0gbG9hZF9zYWxlc19kYXRhKGxvYWQuY2FjaGUgPSBUKQ0KDQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkoUlNvY3JhdGEpDQpsaWJyYXJ5KHNwKQ0KbGlicmFyeShsZWFmbGV0KQ0KDQpyZWFsLnByb3AudXJsIDwtICJodHRwczovL2RhdGEuYmFsdGltb3JlY2l0eS5nb3YvcmVzb3VyY2UvNmFjdC1xenV5Lmpzb24iDQpyZWFsLnByb3AgPC0gcmVhZC5zb2NyYXRhKHJlYWwucHJvcC51cmwsIGFwcF90b2tlbiA9IFZBUlMkU09DUkFUQV9UT0tFTikNCg0KaG9vZHMgPC0gZ2V0X25laWdoYm9yaG9vZF9ib3VuZGFyaWVzKCkNCmhtdCA8LSBsb2FkX2Jsb2NrX2dyb3VwX2RhdGEobG9hZC5jYWNoZSA9IFQpDQpobXQuaG9vZCA8LSByZWFkX2V4Y2VsKCIuLi9kYXRhL3Jhdy9obXQvSE1UIGJ5IE5laWdoYm9yaG9vZCAyMDE3Lnhsc3giKQ0KDQpjb25uLmdpcyA8LSBvZGJjRHJpdmVyQ29ubmVjdCgNCiAgcGFzdGUwKA0KICAgICdkcml2ZXI9e09EQkMgRHJpdmVyIDEzIGZvciBTUUwgU2VydmVyfTsnLA0KICAgICdzZXJ2ZXI9JywgVkFSUyRFR0lTX1NFUlZFUiwgDQogICAgJzt1aWQ9JywgVkFSUyRFR0lTX1NFUlZFUl9VU0VSLA0KICAgICc7cHdkPScsVkFSUyRFR0lTX1NFUlZFUl9QV0QsIA0KICAgICc7ZGF0YWJhc2U9aG91c2luZzt0cnVzdGVkX2Nvbm5lY3Rpb249Tm8nKQ0KKQ0KDQpwZXJtaXRzIDwtIHNxbEZldGNoKGNvbm4uZ2lzLCAiaG91c2luZy50YmxfUGVybWl0IikNCmBgYA0KDQpGaXJzdCBuZWVkIHRvIGpvaW4gdXAgdGhlIHJlYWwgcHJvcGVydHkgZGF0YSAoW09wZW4gQmFsdGltb3JlXShbaHR0cDovL2RhdGEuYmFsdGltb3JlY2l0eS5nb3YvRmluYW5jaWFsL1JlYWwtUHJvcGVydHktVGF4ZXMvMjd3OS11cnR2dG8pKSB0aGUgc2FsZXMgZGF0YSAocHJvdmlkZWQgYnkgU3RldmUsIGFuZCB3aXRoICoqZGVlZCBkYXRlcyBmcm9tIEphbnVhcnkgMSwgMjAxMCB0aHJvdWdoIE9jdG9iZXIgMjAxOCoqKSBzbyB3ZSBoYXZlIGEgbmVpZ2hib3Job29kIGZvciBhcyBtYW55IHNhbGVzIGFzIHdlIGNhbi4NCg0KYGBge3J9DQpzYWxlcyA8LSBzYWxlcyAlPiUgcmVuYW1lKHNhbGVzLmJsb2NrID0gQmxvY2ssIHNhbGVzLmxvdCA9IExvdCkNCnJlYWwucHJvcCA8LSByZWFsLnByb3AgJT4lIHJlbmFtZShyZWFsLmJsb2NrID0gYmxvY2ssIHJlYWwubG90ID0gbG90KQ0KYGBgDQoNCmBgYHtyfQ0KcmVhbC5wcm9wIDwtIHJlYWwucHJvcCAlPiUNCiAgbXV0YXRlKHJlYWwuYmxvY2suY2xlYW4gPSBnc3ViKCJeMCsiLCAiIiwgcmVhbC5ibG9jayksDQogICAgICAgICByZWFsLmxvdC5jbGVhbiA9IGdzdWIoIl4wKyIsICIiLCByZWFsLmxvdCkpDQoNCnNhbGVzIDwtIHNhbGVzICU+JQ0KICBtdXRhdGUoc2FsZXMuYmxvY2suY2xlYW4gPSBnc3ViKCJeMCsiLCAiIiwgc2FsZXMuYmxvY2spLA0KICAgICAgICAgc2FsZXMubG90LmNsZWFuID0gZ3N1YigiXjArIiwgIiIsIHNhbGVzLmxvdCkpDQpgYGANCg0KYGBge3J9DQpzYWxlcyA8LSBzYWxlcyAlPiUNCiAgbGVmdF9qb2luKHJlYWwucHJvcCwgDQogICAgICAgICAgICBieSA9IGMoInNhbGVzLmJsb2NrLmNsZWFuIiA9ICJyZWFsLmJsb2NrLmNsZWFuIiwNCiAgICAgICAgICAgICAgICAgICAic2FsZXMubG90LmNsZWFuIiA9ICJyZWFsLmxvdC5jbGVhbiIpDQogICAgICAgICAgICApDQpgYGANCg0KYGBge3J9DQpzYWxlcyAlPiUgY291bnQoaXMubmEocmVhbC5ibG9jayksIGlzLm5hKHJlYWwubG90KSkNCmBgYA0KDQoxLDE1MyBzYWxlcyBkaWRuJ3QgbWF0Y2ggdG8gYSBibG9jay1sb3QgaW4gdGhlIHJlYWwgcHJvcGVydHkgdGFibGUsIHdoaWNoIG1lYW5zIHRoYXQgdGhlIGJsb2NrLWxvdCBqb2ludGx5IHdhcyBub3QgaW4gdGhlIHJlYWwgcHJvcCB0YWJsZS4gDQoNCkFsc28sIHRoZXJlIGFyZSBhYm91dCAxNiwwMDAgcHJvcGVydGllcyBpbiB0aGUgcmVhbCBwcm9wIHRhYmxlIHRoYXQgZG9uJ3QgaGF2ZSBhIG5laWdoYm9yaG9vZC4gDQoNCmBgYHtyfQ0KcmVhbC5wcm9wICU+JSBjb3VudChpcy5uYShuZWlnaGJvcmhvb2QpKQ0KYGBgDQoNClNvIGFmdGVyIGpvaW5pbmcgd2UgZW5kIHVwIHdpdGggOSw0Mjggc2FsZXMgdGhhdCBkb24ndCBoYXZlIGEgbmVpZ2hib3Job29kLg0KDQpgYGB7cn0NCnNhbGVzICU+JSBjb3VudCghaXMubmEobmVpZ2hib3Job29kKSkNCmBgYA0KDQpUaGUgcmVhbCBwcm9wZXJ0eSB0YWJsZSBhbHNvIGdpdmVzIGlmIGl0IGlzIHByaW5jaXBhbCByZXNpZGVuY2Ugb3Igbm90LCBzbyB3ZSdsbCBhbHNvIGZpbHRlciBmb3IgdGhlIHNhbGVzIHRoYXQgYXJlIGZvciBwcmluY2lwYWwgcmVzaWRlbmNlcy4NCg0KYGBge3J9DQpzYWxlcyAlPiUgY291bnQocmVzY29kZSkNCmBgYA0KDQpEaXN0cmlidXRpb24gb2YgY2l0eS13aWRlIDIwMTggc2FsZXMgcHJpY2VzOg0KDQpgYGB7cn0NCnNhbGVzICU+JQ0KICBmaWx0ZXIoeWVhcihkZWVkLmRhdGUpID09IDIwMTgpICU+JQ0KICBnZ3Bsb3QoYWVzKGBTYWxlcyBQcmljZWApKSArDQogIGdlb21faGlzdG9ncmFtKCkgKw0KICB0aGVtZV9pdGVhbV9nb29nbGVfZG9jcygpICsNCiAgeGxpbShjKDAsIDUwMDAwMCkpDQpgYGANCg0KYGBge3J9DQpxdWFudGlsZShzYWxlcyRgU2FsZXMgUHJpY2VgLCAwLjg1KQ0KYGBgDQogDQojIEJyaW5nIGluIHBlcm1pdCBkYXRhDQoNCmBgYHtyfQ0KcGVybWl0cyAlPiUgZ2xpbXBzZSgpDQpgYGANCg0KYGBge3J9DQpwZXJtaXRzICU+JSBjb3VudChjc21fc3RhdHVzKQ0KYGBgDQoNCmBgYHtyfQ0KcGVybWl0cyAlPiUgY291bnQoY3NtX3R5cGVfd29yaykNCmBgYA0KDQojIE5laWdoYm9yaG9vZCBTdW1tYXJ5IFRhYmxlLCAyMDE1LTIwMTcNCg0KYGBge3J9DQptZWV0LmNyaXRlcmlhIDwtIHNhbGVzICU+JQ0KICBmaWx0ZXIoeWVhcihkZWVkLmRhdGUpICVpbiUgYygyMDE1LCAyMDE2LCAyMDE3KSwNCiAgICAgICAgICFpcy5uYShuZWlnaGJvcmhvb2QpLA0KICAgICAgICAgYEhvdyBDb252ZXllZGAgPT0gMSwNCiAgICAgICAgICFncmVwbCgiTk9UIiwgcmVzY29kZSkpICU+JQ0KICBucm93DQpgYGANCg0KV2UgaGF2ZSBgciBtZWV0LmNyaXRlcmlhYCBzYW1wbGVzIHRvIHdvcmsgd2l0aCB0aGF0IGFyZSBpbiAyMDE1LTIwMTcsIGhhdmUgYSBuZWlnaGJvcmhvb2QsIHdlcmUgYW4gYXJtcy1sZW5ndGggc2FsZSwgYW5kIGFyZSB0aGUgcHJpbmNpcGFsIHJlc2lkZW5jZS4NCg0KYGBge3J9DQpzYWxlcy5zdW1tYXJ5LjE1XzE3LmJ5Lmhvb2QgPC0gc2FsZXMgJT4lDQogIGZpbHRlcih5ZWFyKGRlZWQuZGF0ZSkgJWluJSBjKDIwMTUsIDIwMTYsIDIwMTcpLA0KICAgICAgICAgIWlzLm5hKG5laWdoYm9yaG9vZCksDQogICAgICAgICBgSG93IENvbnZleWVkYCA9PSAxLA0KICAgICAgICAgIWdyZXBsKCJOT1QiLCByZXNjb2RlKSkgJT4lDQogIGdyb3VwX2J5KG5laWdoYm9yaG9vZCkgJT4lDQogIHN1bW1hcmlzZShob29kLm4gPSBuKCksDQogICAgICAgICAgICBob29kLm1lYW4gPSBtZWFuKGBTYWxlcyBQcmljZWApLA0KICAgICAgICAgICAgaG9vZC5tZWRpYW4gPSBtZWRpYW4oYFNhbGVzIFByaWNlYCksDQogICAgICAgICAgICBob29kLnN0ZCA9IHNxcnQoc3VtKChgU2FsZXMgUHJpY2VgLWhvb2QubWVhbileMi8oaG9vZC5uLTEpKSksDQogICAgICAgICAgICBob29kLjk1dGggPSBxdWFudGlsZShgU2FsZXMgUHJpY2VgLCBwcm9icyA9IC45NSksDQogICAgICAgICAgICBob29kLjk4dGggPSBxdWFudGlsZShgU2FsZXMgUHJpY2VgLCBwcm9icyA9IC45OCksDQogICAgICAgICAgICBob29kLjk5dGggPSBxdWFudGlsZShgU2FsZXMgUHJpY2VgLCBwcm9icyA9IC45OSkpDQoNCnNhbGVzLnN1bW1hcnkuMTVfMTcuYnkuaG9vZCAgDQpgYGANCg0KV2hpY2ggbmVpZ2hib3Job29kcyBoYXZlIGxlc3MgdGhhbiAyMCBzYWxlcyBtZWV0aW5nIHRoZSBjcml0ZXJpYT8NCg0KYGBge3J9DQpzYWxlcy5zdW1tYXJ5LjE1XzE3LmJ5Lmhvb2QgJT4lDQogIGZpbHRlcihob29kLm4gPCAyMCkNCmBgYA0KDQo4NCBuZWlnaGJvcmhvb2RzIGhhdmUgbGVzcyB0aGFuIDIwIHNhbGVzIG1lZXRpbmcgdGhlIGNyaXRlcmlhLiBXZSdsbCBleGNsdWRlIHRoZW0gZ29pbmcgZm9yd2FyZCBzbyB3ZSBoYXZlIGEgcmVhc29uYWJsZSBzYW1wbGUgc2l6ZS4NCg0KYGBge3J9DQojIEpvaW4gdGhlIHN1bW1hcmllcyB0byB0aGUgbmVpZ2hib3Job29kIGJvdW5kYXJpZXMNCmhvb2RzQGRhdGEgPC0gaG9vZHNAZGF0YSAlPiUgDQogIGxlZnRfam9pbihzYWxlcy5zdW1tYXJ5LjE1XzE3LmJ5Lmhvb2QsDQogICAgICAgICAgICBieSA9IGMoImxhYmVsIiA9ICJuZWlnaGJvcmhvb2QiKSkNCmBgYA0KDQojIDk4dGggUGVyY2VudGlsZQ0KDQojIyBDcml0ZXJpYSAmIFJlc3VsdHMNCg0KYGBge3J9DQpzYWxlcy5ob29kLjk4dGggPC0gc2FsZXMgJT4lDQogIGxlZnRfam9pbihzYWxlcy5zdW1tYXJ5LjE1XzE3LmJ5Lmhvb2QsDQogICAgICAgICAgICBieSA9IGMoIm5laWdoYm9yaG9vZCIgPSAibmVpZ2hib3Job29kIikpICU+JQ0KICBmaWx0ZXIoeWVhcihkZWVkLmRhdGUpID09IDIwMTgsDQogICAgICAgICBob29kLm4gPj0gMjAsDQogICAgICAgICBgU2FsZXMgUHJpY2VgID49IGhvb2QuOTh0aCwNCiAgICAgICAgIGBIb3cgQ29udmV5ZWRgID09IDEsDQogICAgICAgICAhZ3JlcGwoIk5PVCIsIHJlc2NvZGUpKSAlPiUNCiAgYXJyYW5nZShuZWlnaGJvcmhvb2QpDQoNCnJlc3VsdC5zYWxlcyA8LSBucm93KHNhbGVzLmhvb2QuOTh0aCkNCmBgYA0KDQoqKlRoZXJlIGFyZSBgciByZXN1bHQuc2FsZXNgIHNhbGVzIHRoYXQgbWVldCB0aGUgZm9sbG93aW5nIGNyaXRlcmlhOioqDQoNCi0gRGVlZCBkYXRlIHdhcyBiZXR3ZWVuIEphbnVhcnkgMSwgMjAxOCBhbmQgT2N0b2JlciA1LCAyMDE4DQotIEFybXMtbGVuZ3RoIHNhbGUNCi0gUHJpbmNpcGFsIHJlc2lkZW5jZQ0KLSBOZWlnaGJvcmhvb2QgaGFkIGF0IGxlYXN0IDIwIHNhbGVzDQotIDk4dGggcGVyY2VudGlsZSBmb3Igc2FsZXMgcHJpY2VzIGZvciB0aGVpciBuZWlnaGJvcmhvb2QuDQoNCihJZiB0aGlzIHlpZWxkIGlzbid0IGhpZ2ggZW5vdWdoIHdlIGNhbiBidW1wIGl0IGRvd24gdG8gdGhlIDk1dGggcGVyY2VudGlsZS4pDQoNCg0KYGBge3J9DQpzYWxlcy5ob29kLjk4dGgkbG9uZyA8LSBsYXBwbHkoc2FsZXMuaG9vZC45OHRoJGxvY2F0aW9uLmNvb3JkaW5hdGVzLCBmdW5jdGlvbih4KSB4WzFdKSAlPiUgdW5saXN0KCkNCg0Kc2FsZXMuaG9vZC45OHRoJGxhdCA8LSBsYXBwbHkoc2FsZXMuaG9vZC45OHRoJGxvY2F0aW9uLmNvb3JkaW5hdGVzLCBmdW5jdGlvbih4KSB4WzJdKSAlPiUgdW5saXN0KCkNCmBgYA0KDQoNCmBgYHtyfQ0Kc2FsZXMuaG9vZC45OHRoLmdlbyA8LSBzYWxlcy5ob29kLjk4dGggJT4lIGZpbHRlcighaXMubmEobG9uZykpDQogIA0Kc2FsZXMuaG9vZC45OHRoLmdlbyA8LSBTcGF0aWFsUG9pbnRzRGF0YUZyYW1lKA0KICBzYWxlcy5ob29kLjk4dGguZ2VvICU+JSBzZWxlY3QobG9uZywgbGF0KSwgDQogIHNhbGVzLmhvb2QuOTh0aC5nZW8sDQogIHByb2o0c3RyaW5nID0gQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCArbm9fZGVmcyArZWxscHM9V0dTODQgK3Rvd2dzODQ9MCwwLDAiKSkNCg0Kc2FsZXMuaG9vZC45OHRoLmdlbyA8LSANCiAgc3BUcmFuc2Zvcm0oDQogICAgc2FsZXMuaG9vZC45OHRoLmdlbywgDQogICAgQ1JTb2JqID0gQ1JTKCIraW5pdD1lcHNnOjQzMjYgK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQgK25vX2RlZnMgK2VsbHBzPVdHUzg0ICt0b3dnczg0PTAsMCwwIikNCiAgICApDQoNCmBgYA0KDQojIyBNYXANCg0KYGBge3J9DQpsaWJyYXJ5KGh0bWx0b29scykNCg0KaG9vZHMubGFiZWxzIDwtIHBhc3RlMCgNCiAgaG9vZHMkbGFiZWwsDQogICI8YnI+TWVkaWFuIFNhbGVzLCAyMDE1LTIwMTc6ICIsIGFzLmNoYXJhY3Rlcihob29kcyRob29kLm1lZGlhbikNCiAgDQopDQoNCnNhbGUubGFiZWxzIDwtIHBhc3RlMCgNCiAgc2FsZXMuaG9vZC45OHRoLmdlbyRgSG91c2UgI2AsICIgIiwNCiAgc2FsZXMuaG9vZC45OHRoLmdlbyRgU3RyZWV0IE5hbWVgLCAiICIsDQogIHNhbGVzLmhvb2QuOTh0aC5nZW8kU3VmZml4LCANCiAgIjxicj5TYWxlIFByaWNlIGluIDIwMTg6ICIsIA0KICBhcy5jaGFyYWN0ZXIoc2FsZXMuaG9vZC45OHRoLmdlbyRgU2FsZXMgUHJpY2VgKSwNCiAgIjxicj5OZXcgT3duZXI6ICIsIHNhbGVzLmhvb2QuOTh0aC5nZW8kbmV3Lm93bmVyDQopDQoNCg0KbGVhZmxldCgpICU+JQ0KICBzZXRWaWV3KGxuZyA9IC03Ni42LCBsYXQgPSAzOS4zLCB6b29tID0gMTEpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXJMaXRlKSAlPiUgDQogIGFkZFBvbHlnb25zKGRhdGEgPSBob29kcywgDQogICAgICAgICAgICAgIHdlaWdodCA9IDIsIA0KICAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIsDQogICAgICAgICAgICAgIG9wYWNpdHkgPSAwLjUsDQogICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMCwgDQogICAgICAgICAgICAgIGxhYmVsID0gfmxhcHBseShob29kcy5sYWJlbHMsIEhUTUwpKSAlPiUNCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gc2FsZXMuaG9vZC45OHRoLmdlbywgDQogICAgICAgICAgICAgICAgICAgcmFkaXVzID0gMiwNCiAgICAgICAgICAgICAgICAgICBsYWJlbCA9IH5sYXBwbHkoc2FsZS5sYWJlbHMsIEhUTUwpKQ0KYGBgDQoNCiMjIEZ1bGwgbGlzdA0KDQpgYGB7cn0NCnNhbGVzLmhvb2QuOTh0aCANCmBgYA0KDQojIERldGVjdCBqdW1wcw0KDQpgYGB7cn0NCiNsb29rIGZvciB0ZW1wb3JhbCBqdW1wcyBpbiBwcmljZXMNCmBgYA0KDQojIDk5dGggUGVyY2VudGlsZQ0KDQojIyBDcml0ZXJpYSAmIFJlc3VsdHMNCg0KYGBge3J9DQpzYWxlcy5ob29kLjk5dGggPC0gc2FsZXMgJT4lDQogIGxlZnRfam9pbihzYWxlcy5zdW1tYXJ5LjE1XzE3LmJ5Lmhvb2QsDQogICAgICAgICAgICBieSA9IGMoIm5laWdoYm9yaG9vZCIgPSAibmVpZ2hib3Job29kIikpICU+JQ0KICBmaWx0ZXIoeWVhcihkZWVkLmRhdGUpID09IDIwMTgsDQogICAgICAgICBob29kLm4gPj0gMjAsDQogICAgICAgICBgU2FsZXMgUHJpY2VgID49IGhvb2QuOTl0aCwNCiAgICAgICAgIGBIb3cgQ29udmV5ZWRgID09IDEsDQogICAgICAgICAhZ3JlcGwoIk5PVCIsIHJlc2NvZGUpKSAlPiUNCiAgYXJyYW5nZShuZWlnaGJvcmhvb2QpDQoNCnJlc3VsdC5zYWxlcyA8LSBucm93KHNhbGVzLmhvb2QuOTl0aCkNCmBgYA0KDQoqKlRoZXJlIGFyZSBgciByZXN1bHQuc2FsZXNgIHNhbGVzIHRoYXQgbWVldCB0aGUgZm9sbG93aW5nIGNyaXRlcmlhOioqDQoNCi0gRGVlZCBkYXRlIHdhcyBiZXR3ZWVuIEphbnVhcnkgMSwgMjAxOCBhbmQgT2N0b2JlciA1LCAyMDE4DQotIEFybXMtbGVuZ3RoIHNhbGUNCi0gUHJpbmNpcGFsIHJlc2lkZW5jZQ0KLSBOZWlnaGJvcmhvb2QgaGFkIGF0IGxlYXN0IDIwIHNhbGVzDQotIDk5dGggcGVyY2VudGlsZSBmb3Igc2FsZXMgcHJpY2VzIGZvciB0aGVpciBuZWlnaGJvcmhvb2QuDQoNCihJZiB0aGlzIHlpZWxkIGlzbid0IGhpZ2ggZW5vdWdoIHdlIGNhbiBidW1wIGl0IGRvd24gdG8gdGhlIDk1dGggcGVyY2VudGlsZS4pDQoNCg0KYGBge3J9DQpzYWxlcy5ob29kLjk5dGgkbG9uZyA8LSBsYXBwbHkoc2FsZXMuaG9vZC45OXRoJGxvY2F0aW9uLmNvb3JkaW5hdGVzLCBmdW5jdGlvbih4KSB4WzFdKSAlPiUgdW5saXN0KCkNCg0Kc2FsZXMuaG9vZC45OXRoJGxhdCA8LSBsYXBwbHkoc2FsZXMuaG9vZC45OXRoJGxvY2F0aW9uLmNvb3JkaW5hdGVzLCBmdW5jdGlvbih4KSB4WzJdKSAlPiUgdW5saXN0KCkNCmBgYA0KDQoNCmBgYHtyfQ0Kc2FsZXMuaG9vZC45OXRoLmdlbyA8LSBzYWxlcy5ob29kLjk5dGggJT4lIGZpbHRlcighaXMubmEobG9uZykpDQogIA0Kc2FsZXMuaG9vZC45OXRoLmdlbyA8LSBTcGF0aWFsUG9pbnRzRGF0YUZyYW1lKA0KICBzYWxlcy5ob29kLjk5dGguZ2VvICU+JSBzZWxlY3QobG9uZywgbGF0KSwgDQogIHNhbGVzLmhvb2QuOTl0aC5nZW8sDQogIHByb2o0c3RyaW5nID0gQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCArbm9fZGVmcyArZWxscHM9V0dTODQgK3Rvd2dzODQ9MCwwLDAiKSkNCg0Kc2FsZXMuaG9vZC45OXRoLmdlbyA8LSANCiAgc3BUcmFuc2Zvcm0oDQogICAgc2FsZXMuaG9vZC45OXRoLmdlbywgDQogICAgQ1JTb2JqID0gQ1JTKCIraW5pdD1lcHNnOjQzMjYgK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQgK25vX2RlZnMgK2VsbHBzPVdHUzg0ICt0b3dnczg0PTAsMCwwIikNCiAgICApDQoNCmBgYA0KDQojIyBNYXANCg0KYGBge3J9DQpsaWJyYXJ5KGh0bWx0b29scykNCg0KaG9vZHMubGFiZWxzIDwtIHBhc3RlMCgNCiAgaG9vZHMkbGFiZWwsDQogICI8YnI+TWVkaWFuIFNhbGVzLCAyMDE1LTIwMTc6ICIsIGFzLmNoYXJhY3Rlcihob29kcyRob29kLm1lZGlhbikNCiAgDQopDQoNCnNhbGUubGFiZWxzIDwtIHBhc3RlMCgNCiAgc2FsZXMuaG9vZC45OXRoLmdlbyRgSG91c2UgI2AsICIgIiwNCiAgc2FsZXMuaG9vZC45OXRoLmdlbyRgU3RyZWV0IE5hbWVgLCAiICIsDQogIHNhbGVzLmhvb2QuOTl0aC5nZW8kU3VmZml4LCANCiAgIjxicj5TYWxlIFByaWNlIGluIDIwMTg6ICIsIA0KICBhcy5jaGFyYWN0ZXIoc2FsZXMuaG9vZC45OXRoLmdlbyRgU2FsZXMgUHJpY2VgKSwNCiAgIjxicj5OZXcgT3duZXI6ICIsIHNhbGVzLmhvb2QuOTl0aC5nZW8kbmV3Lm93bmVyDQopDQoNCg0KbGVhZmxldCgpICU+JQ0KICBzZXRWaWV3KGxuZyA9IC03Ni42LCBsYXQgPSAzOS4zLCB6b29tID0gMTEpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXJMaXRlKSAlPiUgDQogIGFkZFBvbHlnb25zKGRhdGEgPSBob29kcywgDQogICAgICAgICAgICAgIHdlaWdodCA9IDIsIA0KICAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIsDQogICAgICAgICAgICAgIG9wYWNpdHkgPSAwLjUsDQogICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMCwgDQogICAgICAgICAgICAgIGxhYmVsID0gfmxhcHBseShob29kcy5sYWJlbHMsIEhUTUwpKSAlPiUNCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gc2FsZXMuaG9vZC45OXRoLmdlbywgDQogICAgICAgICAgICAgICAgICAgcmFkaXVzID0gMiwNCiAgICAgICAgICAgICAgICAgICBsYWJlbCA9IH5sYXBwbHkoc2FsZS5sYWJlbHMsIEhUTUwpKQ0KYGBgDQoNCmBgYHtyfQ0KZW1pbHkubGlzdCA8LSBjKCI0MDExIEJBUlJJTkdUT04iLA0KICAgICAgICAgICAgICAgICJET1JDSEVTVEVSIiwNCiAgICAgICAgICAgICAgICAiMjMxOSBNT05USUNFTExPIikNCg0Kc2FsZXMuaG9vZC45OHRoLmdlb0BkYXRhICU+JSANCiAgZmlsdGVyKGdyZXBsKHBhc3RlKGVtaWx5Lmxpc3QsIGNvbGxhcHNlPSJ8IiksIHByb3BlcnR5YWRkcmVzcykpDQpgYGANCg0KIyBJbiBNaWRkbGUgTmVpZ2hib3Job29kLCA5OXRoIFBlcmNlbnRpbGUgZm9yIE5laWdoYm9yaG9vZCwgT3ZlciAkMjUwaywgT3ZlciAkMTBrIFBlcm1pdCBBY3Rpdml0eQ0KDQpUaGUgZm9sbG93aW5nIGNyaXRlcmlhIGFyZSB1c2VkIGJlbG93Og0KDQotIERlZWQgZGF0ZSB3YXMgYmV0d2VlbiBKYW51YXJ5IDEsIDIwMTggYW5kIE9jdG9iZXIgNSwgMjAxOA0KLSBBcm1zLWxlbmd0aCBzYWxlDQotIFByaW5jaXBhbCByZXNpZGVuY2UNCi0gTmVpZ2hib3Job29kIGhhZCBhdCBsZWFzdCAyMCBzYWxlcw0KLSA5OXRoIHBlcmNlbnRpbGUgZm9yIHNhbGVzIHByaWNlcyBmb3IgdGhlaXIgbmVpZ2hib3Job29kLg0KLSBTYWxlIHByaWNlIG92ZXIgJDI1MCwwMDANCi0gUGVybWl0cyBpc3N1ZWQgYmV0d2VlbiAyMDE3LTIwMTgNCi0gVG90YWwgcGVybWl0IHZhbHVlIGF0IGxlYXN0ICQxMCwwMDANCg0KYGBge3J9DQpwZXJtaXRzLnJlY2VudCA8LSBwZXJtaXRzICU+JSANCiAgZmlsdGVyKGNzbV9pc3N1ZWRfZGF0ZSA+PSAiMjAxNy0wMS0wMSIpICU+JQ0KICBtdXRhdGUocGVybWl0LmJsb2NrLmNsZWFuID0gZ3N1YigiXjArIiwgIiIsIHByY19ibG9ja19ubyksDQogICAgICAgICBwZXJtaXQubG90LmNsZWFuID0gZ3N1YigiXjArIiwgIiIsIHByY19sb3QpKQ0KDQpwZXJtaXRzLnJlY2VudC5zdW1tYXJ5IDwtIHBlcm1pdHMucmVjZW50ICU+JQ0KICBncm91cF9ieShwZXJtaXQuYmxvY2suY2xlYW4sIHBlcm1pdC5sb3QuY2xlYW4pICU+JQ0KICBzdW1tYXJpc2UocGVybWl0LmNvdW50ID0gbigpLA0KICAgICAgICAgICAgcGVybWl0LnRvdGFsLnZhbHVlID0gc3VtKGNzbV9jb3N0LCBuYS5ybSA9IFQpKQ0KYGBgDQoNCmBgYHtyfQ0KbWlkLmhvb2RzIDwtIGhtdC5ob29kICU+JSBmaWx0ZXIoYFByZWRvbWluYW50IENvZGUgSWdub3JpbmcgTm9uLVJlc2lkZW50aWFsYCAlaW4lIGMoIkQiLCAiRSIsICJGIiwgIkciLCAiSCIpKQ0KDQpzYWxlcy45OXRoLm1pZC5ob29kIDwtIHN1YnNldChzYWxlcy5ob29kLjk5dGguZ2VvLCB0b2xvd2VyKG5laWdoYm9yaG9vZCkgJWluJSB0b2xvd2VyKG1pZC5ob29kcyROZWlnaGJvcmhvb2QpKQ0KDQpzYWxlcy45OXRoLm1pZC5ob29kLm92ZXIuMjUwayA8LSBzdWJzZXQoc2FsZXMuOTl0aC5taWQuaG9vZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgU2FsZXMgUHJpY2VgID4gMjUwMDAwKQ0KYGBgDQoNCmBgYHtyfQ0Kc2FsZXMuOTl0aC5taWQuaG9vZC5vdmVyLjI1MGtAZGF0YSAlPiUgbnJvdw0KYGBgDQoNCg0KYGBge3J9DQpzYWxlcy45OXRoLm1pZC5ob29kLm92ZXIuMjUwa0BkYXRhIDwtIHNhbGVzLjk5dGgubWlkLmhvb2Qub3Zlci4yNTBrQGRhdGEgJT4lDQogIGxlZnRfam9pbihwZXJtaXRzLnJlY2VudC5zdW1tYXJ5LCANCiAgICAgICAgICAgIGJ5ID0gYygic2FsZXMuYmxvY2suY2xlYW4iID0gInBlcm1pdC5ibG9jay5jbGVhbiIsDQogICAgICAgICAgICAgICAgICAgInNhbGVzLmxvdC5jbGVhbiIgPSAicGVybWl0LmxvdC5jbGVhbiIpKQ0KYGBgDQoNCkZ1cnRoZXIgZmlsdGVyIGZvciBwZXJtaXQgdmFsdWUgdG90YWxzIG92ZXIgJDEwLDAwMC4NCg0KYGBge3J9DQpzYWxlcy45OXRoLm1pZC5ob29kLm92ZXIuMjUway4xMGsucGVybWl0IDwtIHN1YnNldChzYWxlcy45OXRoLm1pZC5ob29kLm92ZXIuMjUwaywgcGVybWl0LnRvdGFsLnZhbHVlID49IDEwMDAwKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpgYGANCg0KUmVzdWx0cyBpbiAyNyBwcm9wZXJ0aWVzLg0KDQpgYGB7cn0NCnNhbGVzLjk5dGgubWlkLmhvb2Qub3Zlci4yNTBrLjEway5wZXJtaXRAZGF0YSAlPiUgbnJvdw0KYGBgDQoNCg0KYGBge3J9DQoNCm1pZC5ob29kcy5nZW8gPC0gc3Vic2V0KGhvb2RzLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHRvbG93ZXIobGFiZWwpICVpbiUgdG9sb3dlcihtaWQuaG9vZHMkTmVpZ2hib3Job29kKSkNCg0KDQoNCm1pZC5ob29kcy5sYWJlbHMgPC0gcGFzdGUwKA0KICBtaWQuaG9vZHMuZ2VvJGxhYmVsLA0KICAiPGJyPk1lZGlhbiBTYWxlcywgMjAxNS0yMDE3OiAiLCBhcy5jaGFyYWN0ZXIobWlkLmhvb2RzLmdlbyRob29kLm1lZGlhbikNCiAgDQopDQoNCnNhbGUubGFiZWxzIDwtIHBhc3RlMCgNCiAgc2FsZXMuOTl0aC5taWQuaG9vZC5vdmVyLjI1MGsuMTBrLnBlcm1pdCRgSG91c2UgI2AsICIgIiwNCiAgc2FsZXMuOTl0aC5taWQuaG9vZC5vdmVyLjI1MGsuMTBrLnBlcm1pdCRgU3RyZWV0IE5hbWVgLCAiICIsDQogIHNhbGVzLjk5dGgubWlkLmhvb2Qub3Zlci4yNTBrLjEway5wZXJtaXQkU3VmZml4LCANCiAgIjxicj5TYWxlIFByaWNlIGluIDIwMTg6ICIsIA0KICBhcy5jaGFyYWN0ZXIoc2FsZXMuOTl0aC5taWQuaG9vZC5vdmVyLjI1MGsuMTBrLnBlcm1pdCRgU2FsZXMgUHJpY2VgKSwNCiAgIjxicj5OZXcgT3duZXI6ICIsIHNhbGVzLjk5dGgubWlkLmhvb2Qub3Zlci4yNTBrLjEway5wZXJtaXQkbmV3Lm93bmVyLA0KICAiPGJyPlBlcm1pdHMgSXNzdWVkIGZyb20gMjAxNy0yMDE4OiAiLCBzYWxlcy45OXRoLm1pZC5ob29kLm92ZXIuMjUway4xMGsucGVybWl0JHBlcm1pdC5jb3VudCwNCiAgIjxicj5Ub3RhbCBQZXJtaXQgVmFsdWUgZnJvbSAyMDE3LTIwMTg6ICIsIHNhbGVzLjk5dGgubWlkLmhvb2Qub3Zlci4yNTBrLjEway5wZXJtaXQkcGVybWl0LnRvdGFsLnZhbHVlDQopDQoNCg0KbGVhZmxldCgpICU+JQ0KICBzZXRWaWV3KGxuZyA9IC03Ni42LCBsYXQgPSAzOS4zLCB6b29tID0gMTEpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXJMaXRlKSAlPiUgDQogIGFkZFBvbHlnb25zKGRhdGEgPSBob29kcywgDQogICAgICAgICAgICAgIHdlaWdodCA9IDIsIA0KICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siLA0KICAgICAgICAgICAgICBvcGFjaXR5ID0gMC41LA0KICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAsIA0KICAgICAgICAgICAgICBsYWJlbCA9IH5sYXBwbHkoaG9vZHMubGFiZWxzLCBIVE1MKSkgJT4lDQogIGFkZFBvbHlnb25zKGRhdGEgPSBtaWQuaG9vZHMuZ2VvLCANCiAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwgDQogICAgICAgICAgICAgICNjb2xvciA9ICJibGFjayIsDQogICAgICAgICAgICAgIG9wYWNpdHkgPSAwLjAsDQogICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gLjIsDQogICAgICAgICAgICAgIGZpbGxDb2xvciA9IGl0ZWFtLmNvbG9yc1szXSwNCiAgICAgICAgICAgICAgbGFiZWwgPSB+bGFwcGx5KG1pZC5ob29kcy5sYWJlbHMsIEhUTUwpKSAlPiUNCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gc2FsZXMuOTl0aC5taWQuaG9vZC5vdmVyLjI1MGsuMTBrLnBlcm1pdCwgDQogICAgICAgICAgICAgICAgICAgY29sb3IgPSBpdGVhbS5jb2xvcnNbMV0sDQogICAgICAgICAgICAgICAgICAgb3BhY2l0eSA9IDEsDQogICAgICAgICAgICAgICAgICAgcmFkaXVzID0gMiwNCiAgICAgICAgICAgICAgICAgICBsYWJlbCA9IH5sYXBwbHkoc2FsZS5sYWJlbHMsIEhUTUwpKQ0KYGBgDQoNCiMjIEZ1bGwgTGlzdA0KDQpgYGB7cn0NCnNhbGVzLjk5dGgubWlkLmhvb2Qub3Zlci4yNTBrLjEway5wZXJtaXRAZGF0YQ0KYGBgDQoNCg0KDQo=